/*******************************************************************************
 * Copyright 2011 @ Kapil Viren Ahuja
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.kapilvirenahuja.eamsteps.dataprovider.impl;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;

import com.kapil.framework.exception.impl.DataProviderException;
import com.kapil.framework.lang.ObjectFactory;
import com.kapil.framework.logger.ILogger;
import com.kapil.framework.logger.LogFactory;
import com.kapilvirenahuja.eamsteps.dataloader.IDataLoader;
import com.kapilvirenahuja.eamsteps.dataprovider.AbstractDataProvider;
import com.kapilvirenahuja.eamsteps.dataprovider.IDataProvider;
import com.kapilvirenahuja.eamsteps.datareader.IDataCell;
import com.kapilvirenahuja.eamsteps.datareader.IDataRow;
import com.kapilvirenahuja.eamsteps.datareader.IDataSet;
import com.kapilvirenahuja.eamsteps.datareader.IDataTable;
import com.kapilvirenahuja.eamsteps.form.IDataForm;
import com.kapilvirenahuja.eamsteps.info.HeaderInfo;
import com.kapilvirenahuja.eamsteps.parser.ExcelHeaderParser;
import com.kapilvirenahuja.eamsteps.parser.IHeaderParser;


/**
 * <p>
 * Implementation of {@link IDataProvider} which extends from {@link AbstractDataProvider} and adds the capabilities to
 * read from an Excel workbook.
 * </p>
 * 
 * <p>
 * This class uses expects an object of {@link IDataSet} with the data in a specified form. This class assumes that the data
 * provided follows certain rules:
 * <ul>
 * <li>String that define the data boundaries - marking start and end of data</li>
 * <li>Header Format</li>
 * <li>Names of sheets in the source excel workbook which will be ignored</li>
 * </ul>
 * </p>
 * 
 * <p>
 * This class does not holds the functionalities for parsing the header and the data rows from the excel. It delegates
 * the responsibility to {@link ExcelHeaderParser} and {@link DefaultDataLoader} classes for the same. This implementation
 * will be responsible for adding the data into the {@link IDataForm}.
 * </p>
 * 
 */
public final class DefaultDataProvider extends AbstractDataProvider implements IDataProvider
{
    private static final ILogger logger                  = LogFactory.getInstance().getLogger(DefaultDataProvider.class);

    private final List<String>   dataTablesToIgnore      = new ArrayList<String>();

    private static final String  INDICATOR_START_OF_DATA = "--- Start of Data";

    private static final String  INDICATOR_END_OF_DATA   = "--- End of Data";

    private static final String  INDICATOR_HEADER_ROW    = "header";


    private final IHeaderParser  headerParser;
    private final IDataLoader    dataLoader;


    /**
     * The only Constructor for the class which requires certain parameters to work on.
     *
     * @param dataSet - Object of {@link IDataSet} that holds the source data
     * @param form - Object of {@link IDataForm} that will be populated
     * @param headerParser - an object of {@link IHeaderParser} that will be used to parse the header.
     * @param loader - an object of {@link IDataLoader} that will be used to parse and load the rows.
     * 
     */
    public DefaultDataProvider(IDataSet dataSet, IDataForm form, IHeaderParser headerParser, IDataLoader loader)
    {
        super(form);

        // Add the sheets/data-tables that we have to ignore while loading.
        dataTablesToIgnore.add("instructions");
        dataTablesToIgnore.add("template");

        this.headerParser = headerParser;
        this.dataLoader = loader;
        super.setDataSet(dataSet);

        super.validateForm();
        if (!super.isValid())
        {
            throw new DataProviderException("Could not initialize DataProvider because form [" + super.getForm().getClass().getCanonicalName()
                    + "] is invalid.");
        }
    }


    /**
     * @see AbstractDataProvider#isIgnored(String)
     */
    protected Boolean isIgnored(String dataTable)
    {
        return dataTablesToIgnore.contains(dataTable);
    }


    /**
     * @see IDataProvider#readData()
     */
    public IDataForm readData()
    {
        if (!super.isValid())
        {
            throw new DataProviderException("Halting execution becasue validations have failed. Please validate your Form and Excel sheet before proceeding.");
        }


        // Check if the Java class (form) has all the needed setters and adders.
        logger.debug("Validating form getters and adders.");
        for (IDataTable table : getDataSet().getDataTables())
        {
            // Check if the table has been ignored in the provider.
            if (isIgnored(StringUtils.lowerCase(table.getName())))
            {
                logger.debug("Skipping ##", table.getName());
            }
            else
            {
                logger.debug("Reading data for ##", table.getName());
                // this.dataLoader.preLoadInitialization(table.getName(),
                // super.getForm().getObjectType(table.getName()));
                parseRows(table);
            }

            logger.debug(""); // Just to add a new line in the debug log.
        }

        return super.getForm();
    }


    /**
     * @see AbstractDataProvider#parseRows(IDataTable)
     */
    protected void parseRows(IDataTable table)
    {
        HeaderInfo[] header = null;
        boolean startReadingData = false;

        // Setup the data loader
        String tableName = table.getName();


        // Loop over all the rows in the workbook.
        int rowCount = table.getRows().size();
        for (int i = 0; i < rowCount; i++)
        {
            IDataRow row = table.getRow(i);
            IDataCell cell;

            // Get the cell in the 2nd column. This is to check what type of row are we dealing with.
            cell = row.getCell(1);
            if (cell != null)
            {
                String cellValue = cell.getStringValue();


                // Stop the parsing if end of data marker is found.
                if (StringUtils.contains(cellValue.toLowerCase(), INDICATOR_END_OF_DATA.toLowerCase()))
                {
                    logger.debug("Reached end of data in the sheet ##", tableName);
                    return;
                }


                // Check if the start of data marker has been located.
                if (startReadingData)
                {
                    parseRow(table, row, header);

                    // Skip the rest of the logic below as we are right now reading the data.
                    continue;
                }


                // Check if the cell has a header
                if (StringUtils.equalsIgnoreCase(cellValue, INDICATOR_HEADER_ROW))
                {
                    logger.debug("Header Located.");
                    header = headerParser.parseHeader(row, table.getRow(i + 1));

                    // Once header is loaded move to the next row. incrementing the i will also skip loop once saving
                    // some microseconds.
                    i++;
                    continue;
                }


                // Start of Data Indicator
                if (StringUtils.contains(cellValue.toLowerCase(), INDICATOR_START_OF_DATA.toLowerCase()))
                {
                    logger.debug("Start of data marker located. Initiate data rows parsing.");

                    // Set the read data flag to true and skip this row, because we have to skip this row and start
                    // loading from the next one.
                    startReadingData = true;
                }
            }
        }

        throw new DataProviderException("No End of Data Indicator found. Please validate the worksheet.");
    }


    private void parseRow(IDataTable table, IDataRow row, HeaderInfo[] header)
    {
        Object objectInstance = ObjectFactory.createInstance(super.getForm().getObjectType(table.getName()));


        int startCol = 2;
        String rowId = row.getCell(1).getStringValue();

        // loop for all available columns and load them into an object.
        for (int col = 0; col < header.length; col++)
        {
            String value = "";
            if (row.getCell(col + startCol) != null)
            {
                // extract the value.
                value = row.getCell(col + startCol).getStringValue();

                // extract the cell type.
                executeLoadObject(table.getName(), rowId, value, header[col], objectInstance);
            }

            logger.debug("Parsing row [" + rowId + "] with data [" + value + "]");
        }


        if (objectInstance != null)
        {
            super.getForm().addData(table.getName(), rowId, objectInstance);
        }
    }


    /**
     * Delegates the calls to load the cell value into an object.
     * 
     * @param rowId
     * @param data
     * @param header
     * @param objectInstance
     * @return
     */
    private Object executeLoadObject(String dataTableName, String rowId, String data, HeaderInfo header, Object objectInstance)
    {
        this.dataLoader.setup(dataTableName, rowId, data, header, objectInstance, super.getForm());

        return objectInstance;
    }
}
